#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Datetime: 2020/7/28 14:17
# @Author  : CHEN Wang
# @Site    :
# @File    : holding_backtest.py
# @Software: PyCharm

"""
脚本说明： 根据组合历史持仓信息表，拟合组合业绩; 后续这块的看看怎么和quantaxis结合，运用quantaxis的撮合交易框架
"""

import pandas as pd
import numpy as np
import datetime as dt
import matplotlib.pyplot as plt
from quant_researcher.quant.datasource_fetch.stock_api import stock_price_related
from quant_researcher.quant.project_tool import time_tool
from quant_researcher.quant.performance_attribution.core_functions.performance_analysis import performance
from quant_researcher.quant.datasource_fetch.index_api import index_price_related
from quant_researcher.quant.datasource_fetch.fund_api import fund_nav_related
from quant_researcher.quant.datasource_fetch.crypto_api.glassnode import get_ret
from quant_researcher.quant.project_tool.db_operator import db_conn
from quant_researcher.quant.project_tool.logger.my_logger import LOG

plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号


def simple_backtest(asset_weights_df, asset_type, start_date=None, end_date=None, asset_ret_df=None,
                    benchmark=None, benchmark_type=None, benchmark_ret_df=None, self_benchmark=False, if_plot=False, **kwargs):
    """
    根据资产类别和资产持仓权重时间序列

    :param asset_weights_df: 资产权重df, 其中tradedate的格式为date格式， 或str格式
                             权重为这天开盘前的权重
        —————————————
        index |tradedate  | code   | weights
        0    |2010-01-03 | 000001 | 0.5
        1   |2010-01-03 | 000002 | 0.5
        —————————————
    :param asset_type: 资产类别，支持‘index’, 'fund', 'stock', 'manager', 'crypto'
    :param str end_date: 开始时间, '%Y-%m-%d'
    :param str start_date: 结束时间, '%Y-%m-%d'
    :param asset_ret_df: 资产收益率数据，默认为None, 则程序根据资产类型和代码自动获取
        —————————————
        index |tradedate  | code   | ret
        0    |2010-01-03 | 000001 | 0.01
        1   |2010-01-03 | 000002 | 0.05
        —————————————
    :param benchmark: 基准代码, 如'000300', 如果self_benchmark为True, 则输入 'TKFINANCE_SW': {'801780': 1 / 3, '801790': 1 / 3, '801180': 1 / 3}
    :param benchmark_type: 基准类型， 支持‘index’, 'fund', 'stock', 'crypto'
    :param benchmark_ret_df: 基准收益率数据，默认为None, 则程序根据资产类型和benchmark代码自动获取
        —————————————
                    | benchmark_code
        |2010-01-03 |     0.01
        |2010-01-03 |     0.05
        —————————————
    :param bool self_benchmark: 默认为false
    :param bool if_plot: 默认为false
    :param kwargs:
        - float commission: 默认为0，无手续费
        - bool if_save_figure: 默认为False, 图片是否保存
        - str file_name: 图片保存名, 如果为None, 则不保存
        - bool log_ret: 画图展示累计收益，还是对数累计收益
        - str long_short: 只做多‘long_only’, 多空‘long_sort’
        - str freq: 策略频率
    :return:
    """
    freq = kwargs.pop('freq', 'daily')

    if end_date is None:  # 回测的结束时间，没有的话则回测至昨天
        end_date = time_tool.get_yesterday(marker='with_n_dash')

    asset_weights_df = asset_weights_df.groupby(['code', 'tradedate'])['weights'].sum().reset_index()  # 避免某天某个标的有多条权重记录
    weights = asset_weights_df.set_index(['tradedate', 'code'])['weights'].unstack().fillna(0)
    asset_pool = weights.columns.tolist()
    asset_num = len(asset_pool)
    if asset_type in ['stock', 'fund', 'index', 'manager']:
        if 'H11025' not in asset_pool:  # 如果每期权重之和不为1，则多余的权重分配到货基指数上
            weights['H11025'] = 1 - weights.sum(axis=1)  # t每期权重和不为1的话则加到货币基金指数上
        cash = 'H11025'
    elif asset_type == 'crypto':
        if 'USDT' not in asset_pool:  # 如果每期权重之和不为1， 则多余的权重分配到USDT，持有数字货币现金
            weights['USDT'] = 1 - weights.sum(axis=1).abs()  # t每期权重和不为1的话则加到USDT上
            asset_pool.append('USDT')
        cash = 'USDT'

    if isinstance(weights.index[0], str):  # 如果日期格式为str，则转变为timestamp
        date_str_format = time_tool.guess_date_fmt(weights.index[0])
        weights.index = pd.to_datetime(weights.index)
    if start_date is None:  # 回测起始时间未指定，则以权重记录的最早时间开始
        start_date = weights.index.min().strftime(date_str_format)
    last_weight_date = weights.index.max().strftime(date_str_format)
    if last_weight_date > end_date:
        weights = weights.loc[:end_date, :]

    first_asset_weights = weights[asset_pool[0]]
    if asset_num == 1 and set(first_asset_weights.unique()) == {0, 1}:  # 组合除了现金资产，只持有该资产，且该资产权重只有0或者1
        trade_date_list = first_asset_weights[first_asset_weights != first_asset_weights.shift(1).fillna(method='bfill')].index.tolist()
    else:
        trade_date_list = list(weights.index[1:])  # 第一期持仓不算调仓日

    if asset_ret_df is None:
        if asset_type == 'index':
            asset_ret_df = index_price_related.get_index_return(asset_pool, start_date, end_date)
            if asset_ret_df is None:
                return None, None, None, None
            asset_ret_df = asset_ret_df.rename(columns={'end_date': 'tj', 'daily_return': 'i_price'})
            asset_ret_df['tj'] = pd.to_datetime(asset_ret_df['tj'])
            asset_ret_df['tj'] = asset_ret_df['tj'].dt.date
            asset_ret_df = asset_ret_df.set_index(['tj', 'index_code'])['i_price'].unstack()
        elif asset_type == 'fund':
            asset_ret_df = fund_nav_related.get_fund_return(fund_code=asset_pool, start_date=start_date, end_date=end_date)
            if asset_ret_df is None:
                return None, None, None, None
            asset_ret_df['end_date'] = pd.to_datetime(asset_ret_df['end_date'])
            asset_ret_df['end_date'] = asset_ret_df['end_date'].dt.date
            asset_ret_df = asset_ret_df.rename(columns={'end_date': 'tj', 'daily_return': 'f_price'})
            asset_ret_df = asset_ret_df.set_index(['tj', 'fund_code'])['f_price'].unstack()
        elif asset_type == 'manager':
            asset_ret_df = fund_nav_related.get_manager_return(manager_code=asset_pool, start_date=start_date,
                                                               end_date=end_date, fund_type='00')
            if asset_ret_df is None:
                return None, None, None, None
            asset_ret_df['end_date'] = pd.to_datetime(asset_ret_df['end_date'])
            asset_ret_df['end_date'] = asset_ret_df['end_date'].dt.date
            asset_ret_df = asset_ret_df.rename(columns={'end_date': 'tj', 'daily_return': 'f_price'})
            asset_ret_df = asset_ret_df.set_index(['tj', 'manager_code'])['f_price'].unstack()
        elif asset_type == 'stock':
            asset_quote = stock_price_related.get_stock_quote(asset_pool, start_date, end_date,
                                                              select=['stock_code', 'end_date', 'high', 'low', 'close'])
            if asset_quote is None:
                return None, None, None, None
            asset_quote = asset_quote.rename(columns={'stock_code': 'code', 'end_date': 'tradedate'})
            asset_ret_df = asset_quote.set_index(['tradedate', 'code'])['close'].unstack().ffill().pct_change()
        elif asset_type == 'crypto':
            asset_ret_df = get_ret(asset_pool, start_date, end_date)
            asset_ret_df.index = pd.to_datetime(asset_ret_df.index)
        else:
            LOG.error(f'当前asset_type为{asset_type}， 目前仅支持index, fund, stock，crypto, 请重新指定')
        if asset_ret_df.shape[1] != len(asset_pool):
            raise ValueError("部分成分标的行情数据没有取到，请检查标的行情是否支持")
    else:
        asset_ret_df = asset_ret_df.set_index(['tradedate', 'code'])[asset_ret_df.columns[-1]].unstack().fillna(0)
        asset_ret_df.index = pd.to_datetime(asset_ret_df.index)
        asset_ret_df = asset_ret_df.loc[start_date:end_date, :]  # 截取收益率数据

    if asset_type in ['stock', 'fund', 'index', 'manager']:
        if 'H11025' not in asset_pool:
            # 获取货币基金指数收益率
            mmf_index_ret = index_price_related.get_index_return('H11025', start_date, end_date)
            mmf_index_ret['end_date'] = pd.to_datetime(mmf_index_ret['end_date'])
            mmf_index_ret['end_date'] = mmf_index_ret['end_date'].dt.date
            mmf_index_ret = mmf_index_ret.rename(columns={'end_date': 'tj', 'daily_return': 'i_price'})
            mmf_index_ret = mmf_index_ret.set_index(['tj', 'index_code'])['i_price'].unstack()
            # 候选资产收益率数据合并
            total_asset_ret_df = asset_ret_df.merge(mmf_index_ret, how='left', left_index=True, right_index=True)
            total_asset_ret_df = total_asset_ret_df.fillna(0)
        else:
            total_asset_ret_df = asset_ret_df.fillna(0)  # 存在某些日期部分标的有行情，部分没有，因此没有行情的收益填充为0
    else:  # 资产类别为crypto
        total_asset_ret_df = asset_ret_df.fillna(0)  # 存在某些日期部分标的有行情，部分没有，因此没有行情的收益填充为0

    if total_asset_ret_df.empty:
        LOG.error("资产的收益率序列为空，请检查输入")
        return None, None, None, None

    if len(weights.index) != len(total_asset_ret_df.index):  # 权重的天数与收益率天数不相等，则需要根据涨跌幅得到每天的权重
        all_date = weights.index.union(total_asset_ret_df.index)
        # total_asset_ret_df的index一般都为交易日，而weights的index可能不为交易日， 因此需要取并集
        new_weights = weights.reindex(all_date)
        if new_weights.index[-1] != weights.index[-1]:  # 回测最后一天不是权重的最后一天
            rebalance_date = weights.index.union(new_weights.index[-1:])
            flag = True
        else:
            rebalance_date = weights.index
            flag = False
        # 再平衡区间内的每日权重需要根据资产的涨跌幅进行调整
        for i in range(len(rebalance_date) - 1):
            start = rebalance_date[i]
            end = rebalance_date[i + 1]
            temp = new_weights.loc[start:end, :].ffill()
            rebalance_start = temp.index[1]  # 再平衡起始点不需要调整， 因此rebalance_start需要往后移一天
            if flag and i == (len(rebalance_date) - 2):  # 如果回测最后一天不是权重的最后一天， 并且当前分析的是最后一个再平衡区间
                rebalance_end = temp.index[-1]  # rebalance_end为该区间的最后一天
            else:
                rebalance_end = temp.index[-2]  # rebalance_end为该区间的倒数第二天
            adjusted_weights = temp.loc[rebalance_start:rebalance_end, :]
            rebalance_ret_df = total_asset_ret_df.loc[start:rebalance_end, :]
            rebalance_cum_ret_df = (rebalance_ret_df + 1).cumprod().shift(1).dropna()
            adjusted_weights = adjusted_weights * rebalance_cum_ret_df
            adjusted_weights = adjusted_weights.div(adjusted_weights.sum(axis=1), axis=0)
            new_weights.loc[rebalance_start:rebalance_end, :] = adjusted_weights
        new_weights = new_weights.ffill()
        weights = new_weights.reindex(total_asset_ret_df.index)

    if asset_type in ['stock', 'fund', 'index', 'manager']:
        if benchmark is None:
            # 如果未指定基准，则默认使用沪深300
            benchmark = '000300'
            benchmark_type = 'index'
    elif asset_type == 'crypto':
        if benchmark is None:
            # 如果未指定基准，则默认使用BTC
            benchmark = 'BTC'
            benchmark_type = 'crypto'

    if benchmark_ret_df is None:
        if self_benchmark:  # 如果基准是用户自建的混合指数
            benchmark_list = list(benchmark.keys())
            index_ret_df = index_price_related.get_index_return(benchmark_list, start_date, end_date)
            index_ret_df.rename(columns={'end_date': 'date', 'daily_return': 'ret'}, inplace=True)
            benchmark_ret_df = index_price_related.self_index_return(benchmark, index_ret_df)
            benchmark_ret_df.rename(columns={0: f'{benchmark}'}, inplace=True)
            benchmark_ret_df.index = pd.to_datetime(benchmark_ret_df.index)
        else:
            if benchmark_type == 'index':
                benchmark_ret_df = index_price_related.get_index_return(benchmark, start_date, end_date)
                benchmark_ret_df['end_date'] = pd.to_datetime(benchmark_ret_df['end_date'])
                benchmark_ret_df['end_date'] = benchmark_ret_df['end_date'].dt.date
                benchmark_ret_df = benchmark_ret_df.rename(columns={'end_date': 'tj', 'daily_return': 'i_price'})
                benchmark_ret_df = benchmark_ret_df.set_index(['tj', 'index_code'])['i_price'].unstack()

            elif benchmark_type == 'fund':
                benchmark_ret_df = fund_nav_related.get_fund_return(fund_code=benchmark, start_date=start_date, end_date=end_date)
                benchmark_ret_df['end_date'] = pd.to_datetime(benchmark_ret_df['end_date'])
                benchmark_ret_df['end_date'] = benchmark_ret_df['end_date'].dt.date
                benchmark_ret_df = benchmark_ret_df.rename(columns={'end_date': 'tj', 'daily_return': 'f_price'})
                benchmark_ret_df = benchmark_ret_df.set_index(['tj', 'fund_code'])['f_price'].unstack()
            elif benchmark_type == 'stock':
                benchmark_quote = stock_price_related.get_stock_quote(benchmark, start_date, end_date,
                                                                      select=['stock_code', 'end_date', 'high', 'low', 'close'])
                benchmark_quote = benchmark_quote.rename(columns={'stock_code': 'code', 'end_date': 'tradedate'})
                benchmark_ret_df = benchmark_quote.set_index(['tradedate', 'code'])['close'].unstack().ffill().pct_change()
            elif benchmark_type == 'crypto':
                benchmark_ret_df = get_ret(benchmark, start_date, end_date)
                benchmark_ret_df.index = pd.to_datetime(benchmark_ret_df.index)
            else:
                LOG.error(f'当前benchmark_type为{benchmark_type}， 目前仅支持index, fund, stock，请重新指定')
    else:
        benchmark_ret_df.index = pd.to_datetime(benchmark_ret_df.index)
        benchmark_ret_df = benchmark_ret_df.loc[start_date:end_date, :]  # 截取基准收益率数据

    # 多头测试
    weight_ret = total_asset_ret_df * weights
    weight_ret.dropna(how='all', inplace=True)
    long_portfolio_ret = weight_ret.sum(axis=1)

    # 由于组合中某些资产存在非交易日有收益数据，而基准没有，这样比较图画会存在缺失值，同时年化的时候也会有问题
    long_portfolio_ret = long_portfolio_ret.reindex(benchmark_ret_df.index)
    long_portfolio_equity = (long_portfolio_ret + 1).cumprod().rename('Long_Portfolio')
    long_portfolio_perf = performance.simple_perf(long_portfolio_ret, days_365=True, freq=freq)

    benchmark_equity = (benchmark_ret_df + 1).cumprod()[f'{benchmark}'].rename('Benchmark')
    benchmark_perf = performance.simple_perf(benchmark_ret_df[f'{benchmark}'], days_365=True, freq=freq)
    perf = pd.DataFrame({'组合策略绩效': long_portfolio_perf, '基准绩效': benchmark_perf})

    long_short = kwargs.pop('long_short', 'long_only')
    if long_short == 'long_short':
        # 多空测试，除cash资产外的资产，如果权重为0设置为-1
        long_short_weights = weights.copy()
        long_short_weights[asset_pool[:-1]] = long_short_weights[asset_pool[:-1]].replace(0, -1)
        long_short_weight_ret = total_asset_ret_df * long_short_weights
        long_short_weight_ret.dropna(how='all', inplace=True)
        long_short_portfolio_ret = long_short_weight_ret.sum(axis=1)

        long_short_portfolio_ret = long_short_portfolio_ret.reindex(benchmark_ret_df.index)
        long_short_portfolio_equity = (long_short_portfolio_ret + 1).cumprod().rename('Long_short_Portfolio')
        long_short_portfolio_perf = performance.simple_perf(long_short_portfolio_ret, days_365=True, freq=freq)

        long_short_portfolio_perf.index = [f'多空组合{i}' for i in long_short_portfolio_perf.index]
        long_short_portfolio_perf.name = '组合策略绩效'
        long_short_portfolio_perf = pd.DataFrame(long_short_portfolio_perf)
        long_short_portfolio_perf['基准绩效'] = np.NAN
        perf = pd.concat([perf, long_short_portfolio_perf], axis=0)

    log_ret = kwargs.pop('log_ret', False)
    if log_ret:
        display_benchmark_ret = np.log10(benchmark_equity)
    else:
        display_benchmark_ret = benchmark_equity-1

    commission_fee_dict = {}
    commission = kwargs.pop('commission', 0)
    if commission:
        buy_label_list = []
        sell_label_list = []
        for trade_num, trade_date in enumerate(trade_date_list):
            if freq == 'daily':
                previous_date = trade_date - dt.timedelta(days=1)         # previous_date收盘后发生调仓
            elif freq == 'hourly':
                previous_date = trade_date - dt.timedelta(hours=1)         # previous_date收盘后发生调仓
            # previous_date = weights.index[trade_num]
            trading_buy_sell_volume = (weights.loc[trade_date, :] - weights.loc[previous_date, :]).drop(cash)  # 现金部分仓位变化不考虑调仓费
            trading_volume = trading_buy_sell_volume.abs()
            total_trade_volume = trading_volume.sum()
            commission_fee = total_trade_volume * commission
            commission_fee_dict[previous_date] = 1 - commission_fee   # 费用发生在昨天收盘后

            if asset_num == 1 and set(first_asset_weights.unique()) == {0, 1}:  # 只有单资产，且单资产权重要么0，要么1才标记买卖点
                if trading_buy_sell_volume.values > 0:
                    buy_label_list.append([previous_date, display_benchmark_ret[previous_date]])
                elif trading_buy_sell_volume.values < 0:
                    sell_label_list.append([previous_date, display_benchmark_ret[previous_date]])

        commission_fee_series = pd.Series(commission_fee_dict)
        commission_fee_series = commission_fee_series.reindex(weights.index)
        commission_fee_series.fillna(1, inplace=True)

        long_portfolio_ret_with_fee = (1+long_portfolio_ret) * commission_fee_series - 1
        long_portfolio_equity_with_fee = (long_portfolio_ret_with_fee + 1).cumprod().rename('Long_Portfolio_with_fee')
        long_portfolio_perf_with_fee = performance.simple_perf(long_portfolio_ret_with_fee, days_365=True, freq=freq)

        if long_short == 'long_short':
            long_short_portfolio_ret_with_fee = (1+long_short_portfolio_ret) * commission_fee_series - 1
            long_short_portfolio_equity_with_fee = (long_short_portfolio_ret_with_fee + 1).cumprod().rename('Long_short_Portfolio_with_fee')
            long_short_portfolio_perf_with_fee = performance.simple_perf(long_short_portfolio_ret_with_fee, days_365=True, freq=freq)

    else:
        long_portfolio_ret_with_fee = long_portfolio_ret
        long_portfolio_equity_with_fee = long_portfolio_equity
        long_portfolio_perf_with_fee = long_portfolio_perf

        if long_short == 'long_short':
            long_short_portfolio_ret_with_fee = long_short_portfolio_ret
            long_short_portfolio_equity_with_fee = long_short_portfolio_equity
            long_short_portfolio_perf_with_fee = long_short_portfolio_perf

    long_portfolio_perf_with_fee.index = [f'{float(commission) * 100:.2f}%费后{i}' for i in long_portfolio_perf_with_fee.index]
    long_portfolio_perf_with_fee.name = '组合策略绩效'
    long_portfolio_perf_with_fee = pd.DataFrame(long_portfolio_perf_with_fee)
    long_portfolio_perf_with_fee['基准绩效'] = np.NAN
    perf = pd.concat([perf, long_portfolio_perf_with_fee], axis=0)

    if long_short == 'long_short':
        long_short_portfolio_perf_with_fee.index = [f'多空组合{float(commission) * 100:.2f}%费后{i}' for i in long_short_portfolio_perf_with_fee.index]
        long_short_portfolio_perf_with_fee.name = '组合策略绩效'
        long_short_portfolio_perf_with_fee = pd.DataFrame(long_short_portfolio_perf_with_fee)
        long_short_portfolio_perf_with_fee['基准绩效'] = np.NAN
        perf = pd.concat([perf, long_short_portfolio_perf_with_fee], axis=0)

    active_ret = long_portfolio_ret_with_fee - benchmark_ret_df[f'{benchmark}']

    # 主动收益的两种计算方法： 1、计算每天超额收益然后累乘，问题是可能累乘的结果与最终组合累计收益减去基准累计收益不符
    #                        2、直接累计收益率相减，问题是组合在一段时间内同涨同跌，但是因为组合基准基数不同，到时差额在变化
    # active_equity = (active_ret + 1).cumprod().rename('Active')
    active_equity = long_portfolio_equity_with_fee - benchmark_equity + 1        # 主动收益的两种计算方法
    active_perf = performance.simple_perf(active_ret, days_365=True, freq=freq)

    active_perf.index = [f'费后超额{i}' for i in active_perf.index]
    active_perf.name = '组合策略绩效'
    active_perf = pd.DataFrame(active_perf)
    active_perf['基准绩效'] = np.NAN
    perf = pd.concat([perf, active_perf], axis=0)

    portfolio_annualized_ret = float(perf.loc[f'{float(commission) * 100:.2f}%费后年化收益率', '组合策略绩效'].replace('%', ''))
    benchmark_annualized_ret = float(perf.loc[f'年化收益率', '基准绩效'].replace('%', ''))
    active_annualized_ret = portfolio_annualized_ret - benchmark_annualized_ret
    perf.loc['主动管理超额年化收益率', :] = [f"{float(active_annualized_ret):.2f}%", np.NAN]

    # portfolio_annualized_vol = float(perf.loc[f'{float(commission) * 100:.2f}%费后年化波动率', '组合策略绩效'].replace('%', ''))
    # benchmark_annualized_vol = float(perf.loc[f'年化波动率', '基准绩效'].replace('%', ''))
    # active_annualized_vol = portfolio_annualized_vol - benchmark_annualized_vol
    # perf.loc['主动管理超额年化波动率', :] = [f"{float(active_annualized_vol):.2f}%", np.NAN]

    active_annualized_vol = float(perf.loc[f'费后超额年化波动率', '组合策略绩效'].replace('%', ''))
    active_info_ratio = active_annualized_ret/active_annualized_vol if active_annualized_vol else 0
    perf.loc['主动管理信息比率', :] = [float(active_info_ratio), np.NAN]

    active_max_drawdown = float(perf.loc[f'费后超额最大回撤', '组合策略绩效'].replace('%', ''))
    active_carma_ratio = active_annualized_ret/active_max_drawdown if active_max_drawdown else 0
    perf.loc['主动管理卡玛比率', :] = [float(active_carma_ratio), np.NAN]

    perf.rename(columns={'基准绩效': f'[{start_date} - {end_date}] {benchmark}基准绩效'}, inplace=True)

    if_save_figure = kwargs.pop('if_save_figure', False)
    file_name = kwargs.pop('file_name', None)
    if if_plot or if_save_figure:
        f, ax = plt.subplots()

        if log_ret:
            (np.log10(long_portfolio_equity)).plot(ax=ax, style='b', label='组合对数累计收益')
            (np.log10(long_portfolio_equity_with_fee)).plot(ax=ax, style='m', label=f'组合对数累计收益 {float(commission) * 100:.2f}% fee')
            if long_short == 'long_short':
                ax2 = ax.twinx()
                ax3 = ax.twinx()
                (np.log10(long_short_portfolio_equity)).plot(ax=ax2, style='r', label='多空组合对数累计收益')
                (np.log10(long_short_portfolio_equity_with_fee)).plot(ax=ax3, style='c', label=f'多空组合对数累计收益 {float(commission) * 100:.2f}% fee')
            ax4 = ax.twinx()
            (np.log10(long_portfolio_equity_with_fee) - np.log10(benchmark_equity)).plot(ax=ax4, style='k', label='对数超额收益')
            (np.log10(benchmark_equity)).plot(ax=ax, style='y', label=f'{benchmark}基准对数累计收益')
            y_label = 'log_cum_ret'
        else:
            if long_short == 'long_short':
                nav = pd.DataFrame({'组合累计收益': long_portfolio_equity-1,
                                    '多空组合累计收益': long_short_portfolio_equity - 1,
                                    f'组合累计收益 {float(commission) * 100:.2f}% fee': long_portfolio_equity_with_fee-1,
                                    f'多空组合累计收益 {float(commission) * 100:.2f}% fee': long_short_portfolio_equity_with_fee - 1,
                                    f'{benchmark}基准累计收益': benchmark_equity-1,
                                    '超额收益': active_equity-1})
            else:
                nav = pd.DataFrame({'组合累计收益': long_portfolio_equity-1,
                                    '多空组合累计收益': long_short_portfolio_equity - 1,
                                    f'{benchmark}基准累计收益': benchmark_equity-1,
                                    '超额收益': active_equity-1})
            nav.plot(ax=ax)
            y_label = 'cum_ret'

        if asset_num == 1 and set(first_asset_weights.unique()) == {0, 1}:  # 组合除了现金资产，只持有该资产，且该资产权重只有0或者1
            if buy_label_list:  # 如果买入信号非空
                for buy_label in buy_label_list:
                    ax.scatter(buy_label[0], buy_label[1], s=60, marker='^', c='darkorange')
                    # buy_date = buy_label[0].strftime(date_str_format)
                    # plt.annotate(f"{buy_date}", xy=(buy_label[0], buy_label[1]), xytext=(buy_label[0], buy_label[1]+0.5),
                    #              arrowprops=dict(width=0.5, headlength=0.4, headwidth=0.02, color='red', shrink=0.01),
                    #              fontsize=8, color='red')
                    # plt.annotate(f"{buy_date}", xy=(buy_label[0], buy_label[1]), xytext=(buy_label[0], buy_label[1]+1),
                    #              arrowprops=dict(arrowstyle='-'), fontsize=5, color='red')
            if sell_label_list:  # 如果卖出信号非空
                for sell_label in sell_label_list:
                    ax.scatter(sell_label[0], sell_label[1], s=60, marker="v", c='greenyellow')
                    # sell_date = sell_label[0].strftime(date_str_format)
                    # plt.annotate(f"{sell_date}", xy=(sell_label[0], sell_label[1]), xytext=(sell_label[0], sell_label[1]+0.5),
                    #              arrowprops=dict(width=0.5, headlength=0.4, headwidth=0.02, color='green', shrink=0.01),
                    #              fontsize=8, color='green')
                    # plt.annotate(f"{sell_date}", xy=(sell_label[0], sell_label[1]), xytext=(sell_label[0], sell_label[1]+1),
                    #              arrowprops=dict(arrowstyle='-'), fontsize=5, color='green')

        ax.set(title=f'portfolio holding backtest', xlabel='date', ylabel=y_label)
        f.legend()

        if if_save_figure and (file_name is not None):
            plt.savefig(f'{file_name}.png', dpi=1000, bbox_inches='tight')

        if if_plot:
            plt.show()

        f.clf()
        plt.close(f)

    equity_df = pd.concat([long_portfolio_equity, long_portfolio_equity_with_fee, benchmark_equity], axis=1)
    equity_df['log_Long_Portfolio'] = np.log10(equity_df['Long_Portfolio'])
    equity_df['log_Long_Portfolio_with_fee'] = np.log10(equity_df['Long_Portfolio_with_fee'])
    equity_df['log_Benchmark'] = np.log10(equity_df['Benchmark'])

    if_save_excel = kwargs.pop('if_save_excel', True)
    if if_save_excel and (file_name is not None):
        equity_df.to_excel(f'{file_name}.xlsx')

    return long_portfolio_equity, benchmark_equity, long_portfolio_ret, perf


if __name__ == '__main__':
    fundcode = '003095'
    start = '2020-01-31'
    end = time_tool.get_yesterday(marker='with_n_dash')

    # 测试股票持仓回测
    # conn = db_conn.get_derivative_data_conn()
    # components = pd.read_sql(
    #     f"select estimated_date as tradedate, asset_code as code, estimated_weight/100 as weights "
    #     f"from mf_di_fndholding_estimated_day "
    #     f"where fund_code = '{fundcode}' "
    #     f"and estimated_date between '{start}' and '{end}' ", conn)
    #
    # portfolio_equity, benchmark_equity, portfolio_ret, perf = simple_backtest(
    #     asset_weights_df=components,
    #     asset_type='stock',
    #     benchmark=fundcode,
    #     benchmark_type='fund')

    # # 测试基金组合持仓回测
    # components = pd.DataFrame([['2020-03-31', '110022', 0.5], ['2020-03-31', '001410', 0.5],
    #                            ['2020-05-03', '110022', 0.6], ['2020-05-03', '001410', 0.4]],
    #                           columns=['tradedate', 'code', 'weights'])
    #
    # portfolio_equity, benchmark_equity, portfolio_ret, perf = simple_backtest(
    #     asset_weights_df=components,
    #     asset_type='fund',
    #     benchmark=fundcode,
    #     benchmark_type='fund')
    # print(perf)
    # nav = pd.DataFrame({'portfolio': portfolio_equity, 'benchmark': benchmark_equity})
    # nav.plot()
    # plt.show()
    # plt.close()

    # 测试数字货币组合持仓回测
    # components = pd.DataFrame([['2018-01-01', 'BTC', 0.5], ['2018-01-01', 'ETH', 0.5],
    #                            ['2019-01-01', 'BTC', 0.9], ['2019-01-01', 'ETH', 0.1],
    #                            ['2020-05-03', 'BTC', 0.6], ['2020-05-03', 'ETH', 0.4],
    #                            ['2021-04-03', 'BTC', 0.2], ['2021-04-03', 'ETH', 0.8]],
    #                           columns=['tradedate', 'code', 'weights'])

    components = pd.DataFrame([['2018-01-01', 'BTC', 1],
                               ['2018-03-01', 'BTC', 0],
                               ['2018-05-03', 'BTC', 1],
                               ['2019-04-03', 'BTC', 0],
                               ['2019-05-01', 'BTC', 1],
                               ['2019-12-01', 'BTC', 0],
                               ['2020-05-03', 'BTC', 1],
                               ['2021-04-03', 'BTC', 0]],
                              columns=['tradedate', 'code', 'weights'])

    portfolio_equity, benchmark_equity, portfolio_ret, perf = simple_backtest(
        asset_weights_df=components,
        asset_type='crypto',
        benchmark='BTC',
        benchmark_type='crypto',
        if_plot=True,
        file_name=None,
        commission=0.1,
        log_ret=True,
        long_short='long_only')